package org.gpp.proj1.robot;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.gpp.proj1.bone.Bone;
import org.gpp.proj1.bone.BoneStructure;
import org.gpp.proj1.robot.action.executor.ActionExecutor;
import org.gpp.proj1.robot.part.EquippedPart;
import org.gpp.proj1.robot.part.Part;
import org.gpp.proj1.robot.part.SparePart;
import org.gpp.proj1.robot.part.emissor.Emissor;

import com.jme.scene.Node;
import com.jme.scene.Spatial;

/**
 * Class that implements a robot, with skeletal animation
 *  
 * @author Paolo Victor, paolovictor@gmail.com
 */
public class RobotImpl implements Robot {

	/**
	 * The parts that are equipped to this robot. The key is the 
	 * part group and the value is the part
	 */
	protected Map<String, EquippedPart> equippedParts;
	
	/**
	 * The robot's bone structure. Used to attach parts, emissors and
	 * for animation
	 */
	protected BoneStructure boneStructure;
	
	/**
	 * Node that represents the robot on the game scene
	 */
	protected Node robotNode;

	private ActionExecutor currentExecutor;
	
	/**
	 * @param boneStructure
	 */
	public RobotImpl( BoneStructure boneStructure ) {
		assert boneStructure != null : "Cannot create a robot with a null bone structure";
		
		this.robotNode = new Node( "Robot" );
		this.robotNode.attachChild( boneStructure.getRoot() );
		this.boneStructure = boneStructure;
		this.equippedParts = new HashMap<String, EquippedPart>();
		
		this.currentExecutor = null;
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#setPart(java.lang.String, org.gpp.proj1.robot.part.SparePart)
	 */
	public void setPart( String partGroup, SparePart part ) {
		assert partGroup != null : "Cannot set a part for a null group";
		assert part != null : "Cannot set a null part";

		// Sets the bone's models
		Set<String> models = part.getModelNames();
		Map<String, Bone> group = this.boneStructure.getBoneGroup( partGroup );
		
		if( group != null ) {
			for( String modelName : models ) {
				Bone bone = group.get( modelName );					
				Spatial modelNode = part.getModel( modelName );
				
				if( bone != null && modelNode != null ) {
					modelNode.setName( modelName );					
	
					bone.detachChildNamed( "modelNode" );
					
					Node boneModelNode = new Node( "modelNode" );
					boneModelNode.attachChild( part.getModel( modelName ) );
					
					bone.attachChild( boneModelNode );
					
					modelNode.updateRenderState();
				}
			}
			
			EquippedPart equippedPart = new EquippedPart( part, this, group );
			equippedPart.assignGroup( partGroup );
			
			this.equippedParts.put( partGroup, equippedPart );
		}
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#setAnimation(java.lang.String)
	 */
	public void setAnimation( String animation ) {
		assert animation != null : "Cannot set a null animation";
		
		this.boneStructure.getRoot().setAnimation( animation );
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#attachEmissor(java.lang.String, org.gpp.proj1.robot.part.emissor.Emissor)
	 */
	public void attachEmissor( String boneName, Emissor emissor ) {
		assert boneName != null : "Cannot attach an emissor to a bone with a null name";
		assert emissor != null : "Cannot attach a null emissor";

		boneStructure.getBone( boneName ).attachChild( emissor.getEmissorNode( boneName ) );
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#removeEmissor(java.lang.String, org.gpp.proj1.robot.part.emissor.Emissor)
	 */
	public void removeEmissor( String boneName, Emissor emissor ) {
		assert boneName != null : "Cannot remove an emissor to a bone with a null name";
		assert emissor != null : "Cannot remove a null emissor";
		
		boneStructure.getBone( boneName ).detachChildNamed( emissor.getEmissorNode( boneName ).getName() );
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#getNode()
	 */
	public Node getNode() {

		return robotNode;
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#updateAnimation(float)
	 */
	public void updateAnimation(float tpf) {
		
		boneStructure.getRoot().updateAnimation( tpf );
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#getExecutableActions()
	 */
	public Map<String, List<String>> getExecutableActions() {
		Map<String, List<String>> results = new HashMap<String, List<String>>();
		Set<String> groups = equippedParts.keySet();
		
		for( String group : groups ) {
			EquippedPart part = equippedParts.get( group );
			
			if( ! part.getActions().isEmpty() ) {
				results.put( group, part.getActions() );
			}
		}
		
		return results;
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#executeAction(java.lang.String, java.lang.String)
	 */
	public void executeAction( String partGroup, String action ) {
		
		executeAction( partGroup, action, new LinkedList<Robot>() );
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#executeAction(java.lang.String, java.lang.String, java.util.List)
	 */
	public void executeAction( String partGroup, String action, List<Robot> targets ) {
		assert partGroup  != null : "Cannot execute an action from a null group";
		assert action  != null : "Cannot execute a null action";
		assert equippedParts.containsKey( partGroup ) : "The selected part group does not have an equipped part";
		
		EquippedPart part = equippedParts.get( partGroup );
		
		ActionExecutor executor = part.getActionExecutor( action );
		this.currentExecutor = executor;
		
		if( executor != null ) {
			executor.setTargets( targets );
			
			new Thread( executor ).start();
		}
	}

	/* (non-Javadoc)
	 * @see org.gpp.proj1.robot.Robot#getMovementRange()
	 */
	public int getMovementRange() {

		if( ! this.equippedParts.containsKey( Part.LEGS ) ) {
			System.out.println( equippedParts.keySet() );
			return 0;
		}
		
		EquippedPart legs = this.equippedParts.get( Part.LEGS );
		
		return Integer.parseInt( legs.getAttribute( Part.SPEED ) );
	}

	public boolean isIdle() {
		
		return this.currentExecutor == null || this.currentExecutor.isFinished();
	}
}